﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.Errors;


namespace EffiProz.Core.Rights
{
    public class Right
    {
        public bool _isFull;
        public bool isFullSelect;
        public bool isFullInsert;
        public bool isFullUpdate;
        public bool isFullReferences;
        public bool isFullTrigger;
        public bool isFullDelete;
        OrderedHashSet selectColumnSet;
        OrderedHashSet insertColumnSet;
        OrderedHashSet updateColumnSet;
        OrderedHashSet referencesColumnSet;
        OrderedHashSet triggerColumnSet;

        //
        public Right grantableRights;
        public Grantee grantor;
        public Grantee grantee;

        //
        public static OrderedHashSet emptySet = new OrderedHashSet();
        public static Right fullRights = new Right();
        public static Right noRights = new Right();
        static OrderedHashSet fullRightsSet = new OrderedHashSet();

        static Right()
        {
            fullRights.grantor = GranteeManager.systemAuthorisation;
            fullRights._isFull = true;

            fullRightsSet.add(fullRights);
        }

        public static String[] privilegeNames = {
        Tokens.T_SELECT, Tokens.T_INSERT, Tokens.T_UPDATE, Tokens.T_DELETE,
        Tokens.T_REFERENCES, Tokens.T_TRIGGER
    };
        public static int[] privilegeTypes = {
        GrantConstants.SELECT, GrantConstants.INSERT, GrantConstants.UPDATE,
        GrantConstants.DELETE, GrantConstants.REFERENCES,
        GrantConstants.TRIGGER
    };

        public Right()
        {
            this._isFull = false;
        }

        public Right(Table table)
        {

            _isFull = false;
            isFullDelete = true;
            selectColumnSet = table.getColumnNameSet();
            insertColumnSet = table.getColumnNameSet();
            updateColumnSet = table.getColumnNameSet();
            referencesColumnSet = table.getColumnNameSet();
            triggerColumnSet = table.getColumnNameSet();
        }

        public bool isFull()
        {
            return _isFull;
        }

        public Grantee getGrantor()
        {
            return grantor;
        }

        public Grantee getGrantee()
        {
            return grantee;
        }

        public Right getGrantableRights()
        {
            return grantableRights == null ? noRights
                                           : grantableRights;
        }

        public Right duplicate()
        {

            Right right = new Right();

            right.add(this);

            return right;
        }

        /**
         * Supports column level GRANT
         */
        public void add(Right right)
        {

            if (_isFull)
            {
                return;
            }

            if (right._isFull)
            {
                clear();

                _isFull = true;

                return;
            }

            isFullSelect |= right.isFullSelect;
            isFullInsert |= right.isFullInsert;
            isFullUpdate |= right.isFullUpdate;
            isFullReferences |= right.isFullReferences;
            isFullDelete |= right.isFullDelete;

            if (isFullSelect)
            {
                selectColumnSet = null;
            }
            else if (right.selectColumnSet != null)
            {
                if (selectColumnSet == null)
                {
                    selectColumnSet = new OrderedHashSet();
                }

                selectColumnSet.addAll(right.selectColumnSet);
            }

            if (isFullInsert)
            {
                insertColumnSet = null;
            }
            else if (right.insertColumnSet != null)
            {
                if (insertColumnSet == null)
                {
                    insertColumnSet = new OrderedHashSet();
                }

                insertColumnSet.addAll(right.insertColumnSet);
            }

            if (isFullUpdate)
            {
                updateColumnSet = null;
            }
            else if (right.updateColumnSet != null)
            {
                if (updateColumnSet == null)
                {
                    updateColumnSet = new OrderedHashSet();
                }

                updateColumnSet.addAll(right.updateColumnSet);
            }

            if (isFullReferences)
            {
                referencesColumnSet = null;
            }
            else if (right.referencesColumnSet != null)
            {
                if (referencesColumnSet == null)
                {
                    referencesColumnSet = new OrderedHashSet();
                }

                referencesColumnSet.addAll(right.referencesColumnSet);
            }

            if (isFullTrigger)
            {
                triggerColumnSet = null;
            }
            else if (right.triggerColumnSet != null)
            {
                if (triggerColumnSet == null)
                {
                    triggerColumnSet = new OrderedHashSet();
                }

                triggerColumnSet.addAll(right.triggerColumnSet);
            }
        }

        /**
         * supports column level REVOKE
         */
        public void remove(SchemaObject _object, Right right)
        {

            if (right.isFull())
            {
                clear();

                return;
            }

            if (_isFull)
            {
                _isFull = false;
                isFullSelect = isFullInsert = isFullUpdate = isFullReferences =
                    isFullDelete = true;
            }

            if (right.isFullDelete)
            {
                isFullDelete = false;
            }

            if (!isFullSelect && selectColumnSet == null) { }
            else if (right.isFullSelect)
            {
                isFullSelect = false;
                selectColumnSet = null;
            }
            else if (right.selectColumnSet != null)
            {
                if (isFullSelect)
                {
                    isFullSelect = false;
                    selectColumnSet = ((Table)_object).getColumnNameSet();
                }

                selectColumnSet.removeAll(right.selectColumnSet);

                if (selectColumnSet.isEmpty())
                {
                    selectColumnSet = null;
                }
            }

            if (!isFullInsert && insertColumnSet == null) { }
            else if (right.isFullInsert)
            {
                isFullInsert = false;
                insertColumnSet = null;
            }
            else if (right.insertColumnSet != null)
            {
                if (isFullInsert)
                {
                    isFullInsert = false;
                    insertColumnSet = ((Table)_object).getColumnNameSet();
                }

                insertColumnSet.removeAll(right.insertColumnSet);

                if (insertColumnSet.isEmpty())
                {
                    insertColumnSet = null;
                }
            }

            if (!isFullUpdate && updateColumnSet == null) { }
            else if (right.isFullUpdate)
            {
                isFullUpdate = false;
                updateColumnSet = null;
            }
            else if (right.updateColumnSet != null)
            {
                if (isFullUpdate)
                {
                    isFullUpdate = false;
                    updateColumnSet = ((Table)_object).getColumnNameSet();
                }

                updateColumnSet.removeAll(right.updateColumnSet);

                if (updateColumnSet.isEmpty())
                {
                    updateColumnSet = null;
                }
            }

            if (!isFullReferences && referencesColumnSet == null) { }
            else if (right.isFullReferences)
            {
                isFullReferences = false;
                referencesColumnSet = null;
            }
            else if (right.referencesColumnSet != null)
            {
                if (isFullReferences)
                {
                    isFullReferences = false;
                    referencesColumnSet = ((Table)_object).getColumnNameSet();
                }

                referencesColumnSet.removeAll(right.referencesColumnSet);

                if (referencesColumnSet.isEmpty())
                {
                    referencesColumnSet = null;
                }
            }

            if (!isFullTrigger && triggerColumnSet == null) { }
            else if (right.isFullTrigger)
            {
                isFullTrigger = false;
                triggerColumnSet = null;
            }
            else if (right.triggerColumnSet != null)
            {
                if (isFullTrigger)
                {
                    isFullTrigger = false;
                    triggerColumnSet = ((Table)_object).getColumnNameSet();
                }

                triggerColumnSet.removeAll(right.triggerColumnSet);

                if (triggerColumnSet.isEmpty())
                {
                    triggerColumnSet = null;
                }
            }
        }

        void clear()
        {

            _isFull = isFullSelect = isFullInsert = isFullUpdate =
                isFullReferences = isFullDelete = false;
            selectColumnSet = insertColumnSet = updateColumnSet =
                referencesColumnSet = triggerColumnSet = null;
        }

        /**
         * supports column level GRANT / REVOKE
         */
        public bool isEmpty()
        {

            if (_isFull || isFullSelect || isFullInsert || isFullUpdate
                    || isFullReferences || isFullDelete)
            {
                return false;
            }

            if (selectColumnSet != null && !selectColumnSet.isEmpty())
            {
                return false;
            }

            if (insertColumnSet != null && !insertColumnSet.isEmpty())
            {
                return false;
            }

            if (updateColumnSet != null && !updateColumnSet.isEmpty())
            {
                return false;
            }

            if (referencesColumnSet != null && !referencesColumnSet.isEmpty())
            {
                return false;
            }

            if (triggerColumnSet != null && !triggerColumnSet.isEmpty())
            {
                return false;
            }

            return true;
        }

        public OrderedHashSet getColumnsForAllRights(Table table)
        {

            if (_isFull)
            {
                return table.getColumnNameSet();
            }

            if (isFullSelect || isFullInsert || isFullUpdate || isFullReferences)
            {
                return table.getColumnNameSet();
            }

            OrderedHashSet set = new OrderedHashSet();

            if (selectColumnSet != null)
            {
                set.addAll(selectColumnSet);
            }

            if (insertColumnSet != null)
            {
                set.addAll(insertColumnSet);
            }

            if (updateColumnSet != null)
            {
                set.addAll(updateColumnSet);
            }

            if (referencesColumnSet != null)
            {
                set.addAll(referencesColumnSet);
            }

            return set;
        }

        // construction
        public bool contains(Right right)
        {

            if (_isFull)
            {
                return true;
            }

            if (right.isFull())
            {
                return false;
            }

            if (!containsRights(isFullSelect, selectColumnSet,
                                right.selectColumnSet, right.isFullSelect))
            {
                return false;
            }

            if (!containsRights(isFullInsert, insertColumnSet,
                                right.insertColumnSet, right.isFullInsert))
            {
                return false;
            }

            if (!containsRights(isFullUpdate, updateColumnSet,
                                right.updateColumnSet, right.isFullUpdate))
            {
                return false;
            }

            if (!containsRights(isFullReferences, referencesColumnSet,
                                right.referencesColumnSet,
                                right.isFullReferences))
            {
                return false;
            }

            if (!containsRights(isFullTrigger, triggerColumnSet,
                                right.triggerColumnSet, right.isFullTrigger))
            {
                return false;
            }

            if (!isFullDelete && right.isFullDelete)
            {
                return false;
            }

            return true;
        }

        public void removeDroppedColumns(OrderedHashSet columnSet, Table table)
        {

            for (int i = 0; i < columnSet.size(); i++)
            {
                QNameManager.QName name = (QNameManager.QName)columnSet.get(i);

                if (table.findColumn(name.name) >= 0)
                {
                    columnSet.remove(i);

                    i--;
                }
            }
        }

        public OrderedHashSet getColumnsForPrivilege(Table table, int type)
        {

            if (_isFull)
            {
                return table.getColumnNameSet();
            }

            switch (type)
            {

                case GrantConstants.SELECT:
                    return isFullSelect ? table.getColumnNameSet()
                                        : selectColumnSet == null ? emptySet
                                                                  : selectColumnSet;

                case GrantConstants.INSERT:
                    return isFullInsert ? table.getColumnNameSet()
                                        : insertColumnSet == null ? emptySet
                                                                  : insertColumnSet;

                case GrantConstants.UPDATE:
                    return isFullUpdate ? table.getColumnNameSet()
                                        : updateColumnSet == null ? emptySet
                                                                  : updateColumnSet;

                case GrantConstants.REFERENCES:
                    return isFullReferences ? table.getColumnNameSet()
                                            : referencesColumnSet == null
                                              ? emptySet
                                              : referencesColumnSet;

                case GrantConstants.TRIGGER:
                    return isFullTrigger ? table.getColumnNameSet()
                                         : triggerColumnSet == null ? emptySet
                                                                    : triggerColumnSet;
            }

            return emptySet;
        }

        /**
         * Supports column level checks
         */
        public static bool containsAllColumns(OrderedHashSet columnSet, Table table,
                                          bool[] columnCheckList)
        {

            for (int i = 0; i < columnCheckList.Length; i++)
            {
                if (columnCheckList[i])
                {
                    if (columnSet == null)
                    {
                        return false;
                    }

                    if (columnSet.contains(table.getColumn(i).getName()))
                    {
                        continue;
                    }

                    return false;
                }
            }

            return true;
        }

        public static bool containsRights(bool isFull, OrderedHashSet columnSet,
                                      OrderedHashSet otherColumnSet,
                                      bool otherIsFull)
        {

            if (isFull)
            {
                return true;
            }

            if (otherIsFull)
            {
                return false;
            }

            if (otherColumnSet != null
                    && (columnSet == null
                        || !columnSet.containsAll(otherColumnSet)))
            {
                return false;
            }

            return true;
        }

        /**
         * Supports column level rights
         */
        public bool canSelect(Table table, bool[] columnCheckList)
        {

            if (_isFull || isFullSelect)
            {
                return true;
            }

            return containsAllColumns(selectColumnSet, table, columnCheckList);
        }

        /**
         * Supports column level rights
         */
       public bool canInsert(Table table, bool[] columnCheckList)
        {

            if (_isFull || isFullInsert)
            {
                return true;
            }

            return containsAllColumns(insertColumnSet, table, columnCheckList);
        }

        /**
         * Supports column level rights
         */
       public bool canUpdate(Table table, bool[] columnCheckList)
        {

            if (_isFull || isFullUpdate)
            {
                return true;
            }

            return containsAllColumns(updateColumnSet, table, columnCheckList);
        }

        /**
         * Supports column level rights
         */
       public bool canReference(Table table, bool[] columnCheckList)
        {

            if (_isFull || isFullReferences)
            {
                return true;
            }

            return containsAllColumns(referencesColumnSet, table, columnCheckList);
        }

        /**
         * Supports column level rights
         */
       public bool canTrigger(Table table, bool[] columnCheckList)
        {

            if (_isFull || isFullTrigger)
            {
                return true;
            }

            return containsAllColumns(triggerColumnSet, table, columnCheckList);
        }

       public bool canDelete()
        {
            return _isFull || isFullDelete;
        }

        public bool canAccessFully(int privilegeType)
        {

            if (_isFull)
            {
                return true;
            }

            switch (privilegeType)
            {

                case GrantConstants.DELETE:
                    return isFullDelete;

                case GrantConstants.SELECT:
                    return isFullSelect;

                case GrantConstants.INSERT:
                    return isFullInsert;

                case GrantConstants.UPDATE:
                    return isFullUpdate;

                case GrantConstants.REFERENCES:
                    return isFullReferences;

                case GrantConstants.TRIGGER:
                    return isFullTrigger;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Right");
            }
        }

        /**
         * Supports column level rights
         */
        public bool canAccess(int privilegeType)
        {

            if (_isFull)
            {
                return true;
            }

            switch (privilegeType)
            {

                case GrantConstants.DELETE:
                    return isFullDelete;

                case GrantConstants.SELECT:
                    if (isFullSelect)
                    {
                        return true;
                    }

                    return selectColumnSet != null && !selectColumnSet.isEmpty();

                case GrantConstants.INSERT:
                    if (isFullInsert)
                    {
                        return true;
                    }

                    return insertColumnSet != null && !insertColumnSet.isEmpty();

                case GrantConstants.UPDATE:
                    if (isFullUpdate)
                    {
                        return true;
                    }

                    return updateColumnSet != null && !updateColumnSet.isEmpty();

                case GrantConstants.REFERENCES:
                    if (isFullReferences)
                    {
                        return true;
                    }

                    return referencesColumnSet != null
                           && !referencesColumnSet.isEmpty();

                case GrantConstants.TRIGGER:
                    if (isFullTrigger)
                    {
                        return true;
                    }

                    return triggerColumnSet != null && !triggerColumnSet.isEmpty();

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "Right");
            }
        }

        /**
         * supports column level GRANT
         */
       public String getTableRightsSQL(Table table)
        {

            StringBuilder sb = new StringBuilder();

            if (_isFull)
            {
                return Tokens.T_ALL;
            }

            if (isFullSelect)
            {
                sb.Append(Tokens.T_SELECT);
                sb.Append(',');
            }
            else if (selectColumnSet != null)
            {
                sb.Append(Tokens.T_SELECT);
                getColumnList(table, selectColumnSet, sb);
                sb.Append(',');
            }

            if (isFullInsert)
            {
                sb.Append(Tokens.T_INSERT);
                sb.Append(',');
            }
            else if (insertColumnSet != null)
            {
                sb.Append(Tokens.T_INSERT);
                getColumnList(table, insertColumnSet, sb);
                sb.Append(',');
            }

            if (isFullUpdate)
            {
                sb.Append(Tokens.T_UPDATE);
                sb.Append(',');
            }
            else if (updateColumnSet != null)
            {
                sb.Append(Tokens.T_UPDATE);
                getColumnList(table, updateColumnSet, sb);
                sb.Append(',');
            }

            if (isFullDelete)
            {
                sb.Append(Tokens.T_DELETE);
                sb.Append(',');
            }

            if (isFullReferences)
            {
                sb.Append(Tokens.T_REFERENCES);
                sb.Append(',');
            }
            else if (referencesColumnSet != null)
            {
                sb.Append(Tokens.T_REFERENCES);
                sb.Append(',');
            }

            if (isFullTrigger)
            {
                sb.Append(Tokens.T_TRIGGER);
                sb.Append(',');
            }
            else if (triggerColumnSet != null)
            {
                sb.Append(Tokens.T_TRIGGER);
                sb.Append(',');
            }

            return sb.ToString().Substring(0, sb.Length - 1);
        }

        private static void getColumnList(Table t, OrderedHashSet set,
                                          StringBuilder buf)
        {

            int count = 0;
            bool[] colCheckList = t.getNewColumnCheckList();

            for (int i = 0; i < set.size(); i++)
            {
                QNameManager.QName name = (QNameManager.QName)set.get(i);
                int colIndex = t.findColumn(name.name);

                if (colIndex == -1)
                {
                    continue;
                }

                colCheckList[colIndex] = true;

                count++;
            }

            if (count == 0)
            {
                throw Error.runtimeError(ErrorCode.U_S0500, "Right");
            }

            buf.Append('(');

            for (int i = 0, colCount = 0; i < colCheckList.Length; i++)
            {
                if (colCheckList[i])
                {
                    colCount++;

                    buf.Append(t.getColumn(i).getName().statementName);

                    if (colCount < count)
                    {
                        buf.Append(',');
                    }
                }
            }

            buf.Append(')');
        }

        public void setColumns(Table table)
        {

            if (selectColumnSet != null)
            {
                setColumns(table, selectColumnSet);
            }

            if (insertColumnSet != null)
            {
                setColumns(table, insertColumnSet);
            }

            if (updateColumnSet != null)
            {
                setColumns(table, updateColumnSet);
            }

            if (referencesColumnSet != null)
            {
                setColumns(table, referencesColumnSet);
            }

            if (triggerColumnSet != null)
            {
                setColumns(table, triggerColumnSet);
            }
        }

        private static void setColumns(Table t, OrderedHashSet set)
        {

            int count = 0;
            bool[] colCheckList = t.getNewColumnCheckList();

            for (int i = 0; i < set.size(); i++)
            {
                String name = (String)set.get(i);
                int colIndex = t.findColumn(name);

                if (colIndex == -1)
                {
                    throw Error.error(ErrorCode.X_42501, name);
                }

                colCheckList[colIndex] = true;

                count++;
            }

            if (count == 0)
            {
                throw Error.error(ErrorCode.X_42501);
            }

            set.clear();

            for (int i = 0; i < colCheckList.Length; i++)
            {
                if (colCheckList[i])
                {
                    set.add(t.getColumn(i).getName());
                }
            }
        }

        public void set(int type, OrderedHashSet set)
        {

            switch (type)
            {

                case GrantConstants.SELECT:
                    if (set == null)
                    {
                        isFullSelect = true;
                    }

                    selectColumnSet = set;
                    break;

                case GrantConstants.DELETE:
                    if (set == null)
                    {
                        isFullDelete = true;
                    }
                    break;

                case GrantConstants.INSERT:
                    if (set == null)
                    {
                        isFullInsert = true;
                    }

                    insertColumnSet = set;
                    break;

                case GrantConstants.UPDATE:
                    if (set == null)
                    {
                        isFullUpdate = true;
                    }

                    updateColumnSet = set;
                    break;

                case GrantConstants.REFERENCES:
                    if (set == null)
                    {
                        isFullReferences = true;
                    }

                    referencesColumnSet = set;
                    break;

                case GrantConstants.TRIGGER:
                    if (set == null)
                    {
                        isFullTrigger = true;
                    }

                    triggerColumnSet = set;
                    break;
            }
        }

        /**
         * Used solely by org.hsqldb.dbinfo in existing system tables lacking column
         * level reporting.<p>
         *
         * Returns names of individual rights instead of ALL
         */
        String[] getTableRightsArray()
        {

            if (_isFull)
            {
                return new String[] {
                Tokens.T_SELECT, Tokens.T_INSERT, Tokens.T_UPDATE,
                Tokens.T_DELETE, Tokens.T_REFERENCES
            };
            }

            List<string> list = new List<string>();

            if (isFullSelect)
            {
                list.Add(Tokens.T_SELECT);
            }

            if (isFullInsert)
            {
                list.Add(Tokens.T_INSERT);
            }

            if (isFullUpdate)
            {
                list.Add(Tokens.T_UPDATE);
            }

            if (isFullDelete)
            {
                list.Add(Tokens.T_DELETE);
            }

            if (isFullReferences)
            {
                list.Add(Tokens.T_REFERENCES);
            }

            if (isFullTrigger)
            {
                list.Add(Tokens.T_TRIGGER);
            }

            return list.ToArray();


        }
    }
}
